//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
//
// Map view window.
//

# include "StdAfx.h"
# include "Resource.h"
# include "MapView.h"
# include "MiniMap.h"
# include "WebMap.h"

# include "Menu.h"
# include "Status.h"
# include "Toolbar.h"
# include "Settings.h"

# include "Map.h"
# include "Load.h"
# include "RgnProperties.h"
# include "Rename.h"
# include "ToolTransform.h"
# include "ToolRouting.h"
# include "Label.h"

# include "Ruler.h"
# include "Grid.h"
# include "PosCursor.h"

# include "Scale.h"
# include "Position.h"

# include "PrintUnits.h"
# include "Options.h"
# include "Selection.h"
# include "Tool.h"
# include "GPSPanel.h"
# include "Positioning.h"
# include "Cursors.h"
# include "Paths.h"
# include "COMViewEvents.h"
# include "Globals.h"

# define TRACE_WM_PAINT()
# define TRACE_WM_LBUTTONDOWN()
# define TRACE_WM_LBUTTONUP()
# define TRACE_WM_RBUTTONDOWN()
# define TRACE_WM_RBUTTONUP()
# define TRACE_WM_MOUSEMOVE()
# define TRACE_WM_TIMER()

const int c_cTipTimerID     = 10;
const int c_cSizeTimerID    = 11;
const int c_cRefreshTimerID = 12;

typedef void (* pfvv_t) ();

LRESULT CALLBACK _MapWndProc (HWND, UINT, WPARAM, LPARAM);

HWND g_hMapWnd;
HWND g_hMapTipWnd;

static HBITMAP g_hBmp;
HDC g_hMemDC;

# include "MapView.inl"

bool g_bShowRuler;
bool g_bShowLabels;
bool g_bShowGridLines;
bool g_bShowAttachments;
bool g_bTransparentPolygons;
bool g_bShowRGNodes;
bool g_bShowRoadClasses;
bool g_bHighlightAddresses;
bool g_bShowNumbers;
bool g_bShowGoogleMaps;

bool g_bTTShowType;
bool g_bTTShowStreetAndNumber;
bool g_bTTShowCity;
bool g_bTTShowZipCode;
bool g_bTTShowPhone;
bool g_bTTShowComment;

bool InitMapWindow () {
	g_bShowRuler           = GetSetting ("ShowRuler",           true);
	g_bShowLabels          = GetSetting ("ShowLabels",          true);
	g_bShowGridLines       = GetSetting ("ShowGrid",            true);
	g_bTransparentPolygons = GetSetting ("TransparentPolygons", false);
	g_bShowAttachments     = true;
	g_bShowRGNodes         = GetSetting ("ShowRGNodes",         true);
	g_bShowRoadClasses     = GetSetting ("ShowRoadClasses",     false);
	g_bHighlightAddresses  = GetSetting ("HighlightAddresses",  false);
	g_bShowNumbers         = GetSetting ("ShowNumbers",         false);
	g_bShowGoogleMaps      = GetSetting ("ShowGoogleMaps",      false);

	g_bTTShowType            = GetSetting ("TTShowType",            true);
	g_bTTShowStreetAndNumber = GetSetting ("TTShowStreetAndNumber", true);
	g_bTTShowCity            = GetSetting ("TTShowCity",            true);
	g_bTTShowZipCode         = GetSetting ("TTShowZip",             true);
	g_bTTShowPhone           = GetSetting ("TTShowPhone",           true);
	g_bTTShowComment         = GetSetting ("TTShowComment",         false);

	//
	// Estimate size of map window based on sizes of other windows.
	//

	assert (g_hWnd);
	RECT rectClient;
	::GetClientRect (g_hWnd, & rectClient);
	const long lMainW = rectClient.right - rectClient.left;
	const long lMainH = rectClient.bottom - rectClient.top;

	assert (g_hStatusWnd);
	RECT rectStatus;
	::GetClientRect (g_hStatusWnd, & rectStatus);
	const long lStatusH = rectStatus.bottom - rectStatus.top;

	assert (g_hToolbarWnd);
	RECT rectToolbar;
	// NOTE: GetWindowRect() returns incorrect size for toolbar.
	::GetClientRect (g_hToolbarWnd, & rectToolbar);
	const long lToolbarH = rectToolbar.bottom - rectToolbar.top - 1;

	//
	// Register the window class.
	//

	WNDCLASSEX wcex_map;
	::memset (& wcex_map, 0, sizeof (wcex_map));
	wcex_map.cbSize = sizeof (WNDCLASSEX); 
	wcex_map.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
	wcex_map.lpfnWndProc	= (WNDPROC) _MapWndProc;
	wcex_map.hInstance		= g_hResInst;
	wcex_map.hCursor		= g_hPermanentCursor;
	wcex_map.hbrBackground	= g_hbrEmptyMap;
	wcex_map.lpszClassName	= "mapedit_map";
	::RegisterClassEx (& wcex_map);

	//
	// Create the map window.
	//

	g_hMapWnd = ::CreateWindowEx (
		WS_EX_CLIENTEDGE,
		"mapedit_map", NULL,
		WS_CHILD | WS_HSCROLL | WS_VSCROLL | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
		0, lToolbarH, lMainW, lMainH - lStatusH - lToolbarH,
		g_hWnd, NULL, g_hResInst, NULL
	);
	if (! g_hMapWnd)
		return false;

	// Set above toolbar.
	// NOTE: toolbar auto-sizes its window rect incorrectly while client one is OK.
	::SetWindowPos (g_hMapWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

	//
	// Create scrollers.
	//

	SCROLLINFO si;
	si.cbSize = sizeof (si);
	si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
	si.nMin  = 0;
	si.nMax  = 1;
	si.nPage = 2;
	si.nPos  = 0;
	::SetScrollInfo (g_hMapWnd, SB_HORZ, & si, TRUE);
	::SetScrollInfo (g_hMapWnd, SB_VERT, & si, TRUE);

	//
	// Create tooltip for the map window.
	//

	if (! InitTooltip ())
		return false;

	//
	// Create edit box for labels.
	//
	if (! InitLabelEdit ())
		return false;

	//
	// Create the mini-map window.
	//
	if (! InitMiniMap ())
		return false;

	//
	// Create GPS panel.
	//
	if (! InitGPSPanel ())
		return false;

	return true;
}

void DestroyMapWindow () {
	DestroyMiniMap ();

	if (g_hMemDC)
		::DeleteDC (g_hMemDC);
	if (g_hBmp)
		::DeleteObject (g_hBmp);
}

bool InitTooltip () {
	g_hMapTipWnd = ::CreateWindowEx (
		0, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
		g_hMapWnd, NULL, g_hResInst, NULL
	);
	if (! g_hMapTipWnd)
		return false;

	TOOLINFO ti;
	::memset (& ti, 0, sizeof (ti));
	ti.cbSize = sizeof (ti);
	ti.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE | TTF_TRANSPARENT;
	ti.hwnd = g_hMapWnd;
	ti.uId = reinterpret_cast<DWORD> (g_hMapWnd);
	// NOTE: setting this dummy text is required to make tooltip work.
	ti.lpszText = "map";
	::SendMessage (g_hMapTipWnd, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM> (& ti));

	// Enable multi-line behaviour.
	::SendMessage (g_hMapTipWnd, TTM_SETMAXTIPWIDTH, 0, 400);

	return true;
}

////////////////////////////////////////////////

static long g_lPrevWidth, g_lPrevHeight;

static DWORD g_dwLastRefreshTickCount;

static bool g_bInsideRefresh;
static void _OnExitRefresh () {g_bInsideRefresh = false;}

void Refresh () {
	g_bInsideRefresh = true;
	INVOKE_AT_EXIT (_OnExitRefresh);

	//
	// Update scrollers.
	//

	SCROLLINFO si;
	si.cbSize = sizeof (si);
	si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;

	if (g_lWidth > g_lMapWidth) {
		si.nMin  = -c_iMargin;
		si.nMax  = g_lWidth - 1 + c_iMargin;
		si.nPage = g_lMapWidth;
		si.nPos  = (g_lWidth - g_lMapWidth - 1) + g_lX;
	} else {
		si.nMin  = -c_iMargin;
		si.nMax  = g_lMapWidth - 1 + c_iMargin;
		si.nPage = g_lWidth;
		si.nPos  = g_lX;
	}
	::SetScrollInfo (g_hMapWnd, SB_HORZ, & si, TRUE);

	if (g_lHeight > g_lMapHeight) {
		si.nMin  = -c_iMargin;
		si.nMax  = g_lHeight - 1 + c_iMargin;
		si.nPage = g_lMapHeight;
		si.nPos  = - g_lY;
	} else {
		si.nMin  = -c_iMargin;
		si.nMax  = g_lMapHeight - 1 + c_iMargin;
		si.nPage = g_lHeight;
		si.nPos  = g_lMapHeight - g_lHeight - g_lY;
	}
	::SetScrollInfo (g_hMapWnd, SB_VERT, & si, TRUE);

	//
	// Refresh bitmap.
	//

	if (g_lWidth != g_lPrevWidth || g_lHeight != g_lPrevHeight || ! g_hBmp || ! g_hMemDC) {
		// Destroy old resources.
		if (g_hMemDC)
			::DeleteDC (g_hMemDC);
		if (g_hBmp)
			::DeleteObject (g_hBmp);

		// Prepare memory bitmap.
		const CClientDC hClientDC (g_hMapWnd);
		g_hBmp = ::CreateCompatibleBitmap (hClientDC, g_lWidth, g_lHeight);
		g_hMemDC = ::CreateCompatibleDC (hClientDC);
		::SelectObject (g_hMemDC, g_hBmp);

		g_lPrevWidth  = g_lWidth;
		g_lPrevHeight = g_lHeight;
	}

	// Render map to the bitmap.
	{
		CWaiter w;
		CCritSec::CLock lock (g_cs);

		rect_t rectVisible;
		GetVisibleRect (rectVisible);

		const RECT rect = {0, 0, g_lWidth, g_lHeight};

		if (g_map.rectBound.Invalid ()) {
			::FillRect (g_hMemDC, & rect, g_hbrEmptyMap);
		} else {
			SetStatus (GetStr (IDS_STATUS_RENDERING));
			SetProgressStatus (0);

			// Erase background.
			const HBRUSH hbrBG = ::CreateSolidBrush (GetBackgroundColor ());
			CGdiObjectHolder hbrBG_Holder (hbrBG);
			::FillRect (g_hMemDC, & rect, hbrBG);

			// Draw 'active' map.
			if (g_bShowGoogleMaps)
				DrawWebMap (g_hMemDC, rectVisible);
			g_map.Draw (g_hMemDC, rectVisible, 0, 100, g_bShowAttachments);

			DrawPaths   (g_hMemDC, rectVisible);
			DrawCursors (g_hMemDC, rectVisible);

			SetStatusReady ();
		}
	}

	// Copy bitmap to the window.
	Redraw ();

	// Update selected rect in mini-map.
	RedrawMiniMap ();

	// Update coordinates.
	POINT pt;
	::GetCursorPos (& pt);
	::ScreenToClient (g_hMapWnd, & pt);
	point_t point;
	{
		CCritSec::CLock lock (g_cs);
		if (g_map.rectBound.Invalid ())
			return;
		ScreenToPt (pt.x, pt.y, point);
	}
	SetPositionStatus (point);

	g_dwLastRefreshTickCount = ::GetTickCount ();
}

void DelayedRefresh (DWORD _dwTimeout) {
	::KillTimer (g_hMapWnd, c_cRefreshTimerID);

	const DWORD dwElapsed = ::GetTickCount () - g_dwLastRefreshTickCount;
	if (dwElapsed >= _dwTimeout && ! g_bInsideRefresh)
		Refresh ();
	else
		::SetTimer (g_hMapWnd, c_cRefreshTimerID, _dwTimeout - dwElapsed, NULL);
}

void PostRefresh () {
	const pfvv_t pfvv = & Refresh;
	::PostMessage (g_hMapWnd, WM_USER_INVOKE, 0, (LPARAM) pfvv);
}

void Redraw (const RECT * _pRect) {
//	if (g_dwGuiThreadId != ::GetCurrentThreadId ()) {
//		::PostMessage (g_hMapWnd, WM_USER_INVOKE, 0, (LPARAM) Redraw);
//		return;
//	}

	::InvalidateRect (g_hMapWnd, _pRect, FALSE);
	::UpdateWindow (g_hMapWnd);
}

////////////////////////////////////////////////

void ShiftMap (long _lShiftX, long _lShiftY) {
	//
	// Update scroll bars.
	//

	if (_lShiftX != 0) {
		const int nPos = (g_lWidth > g_lMapWidth ? g_lWidth - g_lMapWidth - 1 : 0) + (g_lX + _lShiftX);
		::SetScrollPos (g_hMapWnd, SB_HORZ, nPos, TRUE);
	}
	if (_lShiftY != 0) {
		const int nPos = (g_lHeight > g_lMapHeight ? 0 : g_lMapHeight - g_lHeight - 1) - (g_lY + _lShiftY);
		::SetScrollPos (g_hMapWnd, SB_VERT, nPos, TRUE);
	}

	//
	// Shift map image.
	//

	const CClientDC hDC (g_hMapWnd);

	RECT rect;
	rect.top = 0;
	rect.bottom = g_lHeight;
	if (_lShiftX > 0) {
		rect.left = g_lWidth - _lShiftX;
		rect.right = g_lWidth;
	} else {
		rect.left = 0;
		rect.right = -_lShiftX;
	}
	::FillRect (hDC, & rect, g_hbrTmp);

	rect.left = 0;
	rect.right = g_lWidth;
	if (_lShiftY > 0) {
		rect.top = 0;
		rect.bottom = _lShiftY;
	} else {
		rect.top = g_lHeight + _lShiftY;
		rect.bottom = g_lHeight;
	}
	::FillRect (hDC, & rect, g_hbrTmp);

	::BitBlt (
		hDC,
		-_lShiftX, _lShiftY, g_lWidth, g_lHeight,
		g_hMemDC, 0, 0, SRCCOPY
	);
}

	// Shift in pixels during scrolling.
static long g_lShiftX, g_lShiftY;

void ScrollMap (
	WORD _wActionH, long _nTrackH,
	WORD _wActionV, long _nTrackV
) {
	if (_wActionH == SB_ENDSCROLL || _wActionV == SB_ENDSCROLL) {
		if (g_lShiftX != 0 || g_lShiftY != 0) {
			SetPosition (g_lX + g_lShiftX, g_lY + g_lShiftY);

			g_lShiftX = 0;
			g_lShiftY = 0;

			Refresh ();
		}

		return;
	}

	bool bShift = false;
	if (_wActionH != (WORD)-1) {
		SCROLLINFO si = {sizeof (si), SIF_ALL};
		::GetScrollInfo (g_hMapWnd, SB_HORZ, & si);

		const long lPage = (int) si.nPage - 1;
		const long lMax = si.nMax - lPage;
		long lNewPos = 0;
		switch (_wActionH) {
			case SB_RIGHT:      lNewPos = lMax; break;
			case SB_LEFT:       lNewPos = si.nMin; break;
			case SB_LINERIGHT:  lNewPos = si.nPos + (int) si.nPage/20; break;
			case SB_LINELEFT:   lNewPos = si.nPos - (int) si.nPage/20; break;
			case SB_PAGERIGHT:  lNewPos = si.nPos + (int) si.nPage/2; break;
			case SB_PAGELEFT:   lNewPos = si.nPos - (int) si.nPage/2; break;
			case SB_THUMBTRACK:	lNewPos = _nTrackH; break;
			case SB_THUMBPOSITION: return;
		}
		if (lNewPos > lMax)
			lNewPos = lMax;
		if (lNewPos < si.nMin)
			lNewPos = si.nMin;

		if (lNewPos != si.nPos) {
			g_lShiftX += lNewPos - si.nPos;
			bShift = true;
		}
	}
	
	if (_wActionV != (WORD)-1) {
		SCROLLINFO si = {sizeof (si), SIF_ALL};
		::GetScrollInfo (g_hMapWnd, SB_VERT, & si);

		const long lPage = (int) si.nPage - 1;
		const long lMax = si.nMax - lPage;
		const long lPrevPos = lMax - si.nPos;
		long lNewPos = 0;
		switch (_wActionV) {
			case SB_TOP:        lNewPos = lMax; break;
			case SB_BOTTOM:     lNewPos = si.nMin; break;
			case SB_LINEUP:     lNewPos = lPrevPos + lPage/20; break;
			case SB_LINEDOWN:   lNewPos = lPrevPos - lPage/20; break;
			case SB_PAGEUP:     lNewPos = lPrevPos + lPage/2; break;
			case SB_PAGEDOWN:   lNewPos = lPrevPos - lPage/2; break;
			case SB_THUMBTRACK:	lNewPos = lMax - _nTrackV; break;
			case SB_THUMBPOSITION: return;
		}
		if (lNewPos > lMax + c_iMargin)
			lNewPos = lMax + c_iMargin;
		if (lNewPos < 0)
			lNewPos = 0;

		if (lNewPos != lPrevPos) {
			g_lShiftY += lNewPos - lPrevPos;
			bShift = true;
		}
	}

	// Shift the map image.
	if (bShift)
		ShiftMap (g_lShiftX, g_lShiftY);
}

////////////////////////////////////////////////

const int c_cTipTimerTimeout = 500;

static bool g_bMapToolipVisible = false;

static
void _ActivateMapTooltip (bool _bActivate) {
	if (_bActivate == g_bMapToolipVisible)
		return;
	g_bMapToolipVisible = _bActivate;

	assert (g_hMapWnd);

	TOOLINFO ti;
	::memset (& ti, 0, sizeof (ti));
	ti.cbSize = sizeof (ti);
	ti.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE | TTF_TRANSPARENT;
	ti.hwnd = g_hMapWnd;
	ti.uId = reinterpret_cast<DWORD> (g_hMapWnd);
	ti.lpszText = "map";
	::SendMessage (g_hMapTipWnd, TTM_TRACKACTIVATE, _bActivate ? TRUE : FALSE, reinterpret_cast<LPARAM> (& ti));
}

void _GetMaptipText (const find_info_t & _fi, string_t & _strText);

////////////////////////////////////////////////

const int c_cSizeTimerTimeout = 100;

static
LRESULT CALLBACK _MapWndProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
//		case WM_GETDLGCODE:
//			return DLGC_WANTALLKEYS;

		case WM_PAINT: {
			PAINTSTRUCT ps;
			const HDC hDC = ::BeginPaint (_hWnd, & ps);

			// Draw cached map image.
			::BitBlt (hDC, 0, 0, g_lWidth, g_lHeight, g_hMemDC, 0, 0, SRCCOPY);
			DrawSelection (hDC);

			CTool::_OnPaint (hDC, ps.rcPaint);

			// Show grid.
			if (g_bShowGridLines) {
				RECT rect;
				::GetClientRect (_hWnd, & rect);
				ShowGrid (hDC, rect);
			}

			// Show ruler.
			if (g_bShowRuler) {
				RECT rect;
				::GetClientRect (_hWnd, & rect);
				ShowRuler (hDC, rect.left + 20, rect.bottom - 40);
			}

			// Show position cursor.
			if (IsGPSUnitActive ())
				ShowPosCursor (hDC);

			TRACE_WM_PAINT ();

			::EndPaint (_hWnd, & ps);

			// Hide map's tooltip for time-out.
			_ActivateMapTooltip (false);
			::SetTimer (_hWnd, c_cTipTimerID, c_cTipTimerTimeout, NULL);

			break;
		}

			// Re-create the buffer bitmap for the changed color depth.
		case WM_DISPLAYCHANGE: Refresh (); break;

		case WM_SIZING:
			return TRUE;

		case WM_SIZE: {
			// NOTE: the parent may be not always g_hWnd.
			const HWND hwndParent = ::GetParent (_hWnd);
			if (hwndParent == NULL || ::IsIconic (hwndParent))
				break;

			const long lWidth  = LOWORD (_lParam);
			const long lHeight = HIWORD (_lParam);
			if (lWidth == g_lWidth && lHeight == g_lHeight)
				break;

			::KillTimer (_hWnd, c_cSizeTimerID);
			::SetTimer  (_hWnd, c_cSizeTimerID, c_cSizeTimerTimeout, NULL);

			g_lWidth  = lWidth;
			g_lHeight = lHeight;

			break;
		}

		case WM_SETCURSOR:
			if (reinterpret_cast<HWND> (_wParam) == _hWnd && LOWORD (_lParam) == HTCLIENT)
				::SetCursor (g_hCursor);
			else
				::SetCursor (g_hArrowCursor);
			return TRUE;

		case WM_HSCROLL: {
			SCROLLINFO si = {sizeof (SCROLLINFO), SIF_TRACKPOS};
			::GetScrollInfo (_hWnd, SB_HORZ, & si);

			ScrollMap (LOWORD (_wParam), /*(short) HIWORD (_wParam)*/si.nTrackPos, -1, 0);
			return FALSE;
		}

		case WM_VSCROLL: {
			SCROLLINFO si = {sizeof (SCROLLINFO), SIF_TRACKPOS};
			::GetScrollInfo (_hWnd, SB_VERT, & si);

			ScrollMap (-1, 0, LOWORD (_wParam), /*(short) HIWORD (_wParam)*/si.nTrackPos);
			return FALSE;
		}

			// Begin of drag-and-drop.
		case WM_LBUTTONDOWN: {
			TRACE_WM_LBUTTONDOWN ();
			// Return keyboard input from label edit box.
			//if (g_bDialogMode)
				::SetFocus (g_hWnd);

			// Disable tooltip timer.
			::KillTimer (_hWnd, c_cTipTimerID);

			if (g_bLoading)
				break;

			const int x = GET_X_LPARAM (_lParam);
			const int y = GET_Y_LPARAM (_lParam);

			if (Fire_OnLeftDown (x, y))
				break;
			CTool::_OnLDown (x, y, _wParam);

			break;
		}

		case WM_LBUTTONUP: {
			TRACE_WM_LBUTTONUP ();
			const int x = GET_X_LPARAM (_lParam);
			const int y = GET_Y_LPARAM (_lParam);

			if (Fire_OnLeftUp (x, y))
				break;
			CTool::_OnLUp (x, y, _wParam);

			break;
		}

		case WM_MOUSEMOVE: {
			TRACE_WM_MOUSEMOVE ();
			if (g_bLoading)
				break;

			// NOTE: Tooltip activation has side effect: the window gets WM_MOUSEMOVE.
			static LPARAM s_lOldXY = 0;
			if (s_lOldXY == _lParam)
				break;
			s_lOldXY = _lParam;

			// Hide map's tooltip if cursor moves indeed.
			_ActivateMapTooltip (false);

			// Restart tooltip timer.
			if (! CTool::PendingDND ())
				::SetTimer (_hWnd, c_cTipTimerID, c_cTipTimerTimeout, NULL);

			if (CTool::GetCurrentToolId () != toolDrag || ! CTool::PendingDND ()) {
				point_t point;
				{
					CCritSec::CLock lock (g_cs);
					if (g_map.rectBound.Invalid ())
						break;
					ScreenToPt (GET_X_LPARAM (_lParam), GET_Y_LPARAM (_lParam), point);
				}
				SetPositionStatus (point);
			}

			CTool::_OnMove (GET_X_LPARAM (_lParam), GET_Y_LPARAM (_lParam), _wParam);
			break;
		}

		case WM_LBUTTONDBLCLK: {
			if (g_bLoading)
				break;

			const int x = GET_X_LPARAM (_lParam);
			const int y = GET_Y_LPARAM (_lParam);

			if (Fire_OnDoubleClick (x, y))
				break;
			CTool::_OnLDblClk (x, y, _wParam);

			break;
		}

		case WM_RBUTTONDOWN: {
			TRACE_WM_RBUTTONDOWN ();
			// Return keyboard input from label edit box.
			//if (g_bDialogMode)
				::SetFocus (g_hWnd);

			const int x = GET_X_LPARAM (_lParam);
			const int y = GET_Y_LPARAM (_lParam);

			if (Fire_OnRightDown (x, y))
				break;
			CTool::_OnRDown (x, y, _wParam);

			break;
		}

		case WM_RBUTTONUP: {
			TRACE_WM_RBUTTONUP ();
			if (CTool::PendingDND () || g_bLoading)
				break;

			const int x = GET_X_LPARAM (_lParam);
			const int y = GET_Y_LPARAM (_lParam);

			if (Fire_OnRightUp (x, y))
				break;
			CTool::_OnRUp (GET_X_LPARAM (_lParam), GET_Y_LPARAM (_lParam), _wParam);

			break;
		}

		case WM_TIMER:
			TRACE_WM_TIMER ();

			switch (_wParam) {
				case c_cSizeTimerID:
					::KillTimer (_hWnd, c_cSizeTimerID);

					if (g_bLoading)
						break;

					RefreshScales ();
					Refresh ();

					break;

				case c_cTipTimerID: {
					::KillTimer (_hWnd, c_cTipTimerID);

					POINT pt;
					::GetCursorPos (& pt);

					if (::WindowFromPoint (pt) != g_hMapWnd)
						break;

					POINT ptClient = pt;
					::ScreenToClient (g_hMapWnd, & ptClient);
					if (
						0 > ptClient.x || ptClient.x > g_lWidth ||
						0 > ptClient.y || ptClient.y > g_lHeight
					)
						break;

					string_t strMaptip;
					// Get cursor tip, if any.
					Fire_OnHover (ptClient.x, ptClient.y, strMaptip);
					if (strMaptip.empty ()) {
						// Get map object under mouse cursor.
						const find_info_t fi = FindRgn (ptClient.x, ptClient.y);
						if (! fi.empty ())
							_GetMaptipText (fi, strMaptip);
					}

					if (strMaptip.empty ()) {
						// Hide map's tooltip.
						_ActivateMapTooltip (false);
						break;
					}

					// Buffer for the map's tooltip text.
					static string_t s_strMaptip;
					s_strMaptip.swap (strMaptip);

					// Set map's tooltip position.
					RECT rectTip;
					::GetClientRect (g_hMapTipWnd, & rectTip);
					const size_t cTipH = rectTip.bottom - rectTip.top;

					::SendMessage (g_hMapTipWnd, TTM_TRACKPOSITION, 0, MAKELONG (pt.x + 3, pt.y - cTipH - 3));

					// Set the text.
					TOOLINFO ti;
					::memset (& ti, 0, sizeof (ti));
					ti.cbSize = sizeof (ti);
					ti.uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE | TTF_TRANSPARENT;
					ti.hwnd = g_hMapWnd;
					ti.uId = reinterpret_cast<DWORD> (g_hMapWnd);
					ti.lpszText = const_cast<char *> (s_strMaptip.c_str ());
					::SendMessage (g_hMapTipWnd, TTM_UPDATETIPTEXT, 0, reinterpret_cast<LPARAM> (& ti));

					_ActivateMapTooltip (true);
					break;
				}

				case c_cRefreshTimerID:
					::KillTimer (_hWnd, c_cRefreshTimerID);
					Refresh ();
					break;
			}
			break;

		case WM_USER_INVOKE: {
			const pfvv_t pfvv = reinterpret_cast<pfvv_t> (_lParam);
			if (pfvv)
				pfvv ();
			break;
		}

		default:
			return ::DefWindowProc (_hWnd, _uMsg, _wParam, _lParam);
   }

   return FALSE;
}

//////////////////////////////////////////////////////

# include "Types.h"

static
void _GetMaptipText (const find_info_t & _fi, string_t & _strText) {
	if (_fi.pRenderer) {
		_strText = _fi.pRenderer->strFileName;
		return;
	}

	assert (_fi.pLayer != NULL);
	if (_fi.pLayer == NULL)
		return;
	const rgn_t & rgn = * _fi.iRgn;
	const type_desc_t * const pTD = rgn.GetTypeDesc ();
	assert (pTD);
	if (! pTD)
		return;

	const char * strTypeName = pTD->strTypeName;
	if (pTD->bUnknown) {
		if (rgn.IsWaypoint ())
			strTypeName = "Waypoint";
		else if (rgn.IsTrack ())
			strTypeName = "Track";
		else if (rgn.IsRoute ())
			strTypeName = "Route";
	}

	// Label.
	if (! rgn.strLabel.empty ()) {
		// TODO: handle such strings as well.
		if (rgn.strLabel.size () >= 4 && ::_strnicmp (rgn.strLabel.c_str (), "~[0x", 4) == 0) {
			if (rgn.strLabel.size () > 6 && rgn.strLabel [6] == ']')
				_strText.append (rgn.strLabel.c_str () + 7);
			else if (rgn.strLabel.size () > 8 && rgn.strLabel [8] == ']')
				_strText.append (rgn.strLabel.c_str () + 9);
			else
				_strText.append (rgn.strLabel);
		} else
			_strText.append (rgn.strLabel);
	}

	// Type.
	if (g_bTTShowType) {
		if (_strText.empty ()) {
			_strText = strTypeName;
		} else {
			_strText.append ("\r\n(");
			_strText.append (strTypeName);
			_strText.append (")");
		}
	}

	if (rgn.pExtra) {
		if (g_bTTShowStreetAndNumber) {
			bool bEmptyLine = true;

			// Number.
			if (! rgn.pExtra->strNumber.empty ()) {
				_strText.append ("\r\n");
				_strText.append (rgn.pExtra->strNumber);
				bEmptyLine = false;
			}
			// Street.
			if (! rgn.pExtra->strStreetDesc.empty ()) {
				_strText.append (bEmptyLine ? "\r\n" : ", ");
				_strText.append (rgn.pExtra->strStreetDesc);
			}
		}

		// City/region/country.
		if (g_bTTShowCity) {
			if (rgn.pExtra->pCity && ! rgn.pExtra->pCity->IsDeleted ()) {
				_strText.append ("\r\n");
				const city_t & city = * rgn.pExtra->pCity;
				_strText.append (city.strName);

				_strText.append (", ");
				const region_t & region = * city.pRegion;
				_strText.append (region.strAbbrevName.empty () ? region.strName : region.strAbbrevName);

				_strText.append (", ");
				const country_t & country = * region.pCountry;
				_strText.append (country.strAbbrevName.empty () ? country.strName : country.strAbbrevName);
			}
		}

		// Zip code.
		if (g_bTTShowZipCode) {
			if (rgn.pExtra->pZip && ! rgn.pExtra->pZip->IsDeleted ()) {
				_strText.append ("\r\n");
				_strText.append (rgn.pExtra->pZip->strName);
			}
		}

		// Phone number.
		if (g_bTTShowPhone) {
			if (! rgn.pExtra->strPhone.empty ()) {
				_strText.append ("\r\n");
				_strText.append (rgn.pExtra->strPhone);
			}
		}
	}

	// Comment.
	if (g_bTTShowComment) {
		if (! rgn.strComment.empty ()) {
			_strText.append ("\r\n");
			_strText.append (rgn.strComment);
		}
	}

	// Track point attributes.
	if (rgn.IsTrack ()) {
		if (_fi.cElementOrOrigin < rgn.elements.size ()) {
			const rgn_element_t & element = rgn.elements [_fi.cElementOrOrigin];
			const points_t & points = element.points;
			const rgn_element_extra_t * const pTrackInfo = element.pExtra ? & * element.pExtra : NULL;
			if (pTrackInfo && _fi.cPoint < pTrackInfo->TrackPoints.size () && _fi.cPoint < points.size ()) {
				const trackpoint_info_t & tpi = pTrackInfo->TrackPoints [_fi.cPoint];

				// Time.
				if (tpi.wFlags & trackpoint_info_t::defTime) {
					// Get time zone bias + daylight saving time bias.
					TIME_ZONE_INFORMATION tzi;
					if (::GetTimeZoneInformation (& tzi) == TIME_ZONE_ID_DAYLIGHT)
						tzi.Bias += tzi.DaylightBias;

					// Convert to local time.
					const DATE dtLocalTime = tpi.dtTime - float (tzi.Bias)/(24*60);

					SYSTEMTIME st;
					::VariantTimeToSystemTime (dtLocalTime, & st);

					char strDate [32];
					if (! ::GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, & st, NULL, strDate, sizeof (strDate)/sizeof (strDate [0])))
						strDate [0] = '\0';
					char strTime [32];
					if (! ::GetTimeFormat (LOCALE_USER_DEFAULT, 0, & st, NULL, strTime, sizeof (strTime)/sizeof (strTime [0])))
						strTime [0] = '\0';

					if (strDate [0] != '\0' || strTime [0] != '\0') {
						_strText.append ("\r\n");
						_strText.append (strDate);
						_strText.append (" ");
						_strText.append (strTime);
					}
				}

				// Speed.
				const float fSpeed = pTrackInfo->GetSpeed (_fi.cPoint, points);
				if (fSpeed >= 0) {
					char strSpeed [20], strSpeedUnits [10];
					PrintSpeed (strSpeed, strSpeedUnits, fSpeed);

					_strText.append ("\r\n");
					_strText.append (strSpeed);
					_strText.append (" ");
					_strText.append (strSpeedUnits);
				}
			}
		}
	}
}
